#include "ShadowManager.h"

#include "OpenGLHelpers.h" // glu replecements

void ShadowManager::init(int screenWid, int screenHei)
{
	g_LightPosition[0] = 0;
	g_LightPosition[1] = 2;
	g_LightPosition[2] = 5;
	g_LightPosition[3] = 0;

	g_LightView[0] = 0;
	g_LightView[1] = 0;
	g_LightView[2] = 1;
	glusLookAtf(g_mModelView, g_LightPosition[0],  g_LightPosition[1],  g_LightPosition[2], g_LightView[0],	   g_LightView[1],	    g_LightView[2],		0, 1, 0);

	// 8,2
	// 2,4
	offsetFactor = 1;
	offsetUnits = 2;
	shadowNear = 0.25f;
	shadowFar = 60.0f;
	this->screenWid = screenWid;
	this->screenHei = screenHei;
	shadowWid = std::min<int>( screenWid, screenHei );
	shadowHei = shadowWid;

	// Here we allocate memory for our depth texture that will store our light's view
	slowTextureCopyMode = false;
	if( slowTextureCopyMode )
	{
		CreateRenderTexture(shadowWid, shadowHei, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT);
	}else{
		// can afford large fbo
		shadowWid = shadowHei = 1024*1;
		frameBufferObject.initShadowFBO(shadowWid, screenHei);
	}

	//g_Shader.install("shaders/ShadowMapping.vert", "shaders/ShadowMapping.frag");
	g_Shader.install("shaders/LightAndShadow.vert", "shaders/LightAndShadow.frag");
}

void ShadowManager::UpdateShadowLightMatrices()
{	
	//glusLookAtf(g_mModelView, g_LightPosition[0],  g_LightPosition[1],  g_LightPosition[2], g_LightView[0],	   g_LightView[1],	    g_LightView[2],		0, 1, 0);
	glusPerspectivef(g_mProjection, 40.0f, 1.0f,  shadowNear, shadowFar); 
}

void ShadowManager::CreateRenderTexture(int sizeX, int sizeY, int channels, int type)										
{
	// Create a pointer to store the blank image data
	unsigned int *pTexture = NULL;											

	// Store the current channels to be allocated by default
	int channelsTrue = channels;

	// If the channels are greater than 4 there must be a special flag like
	// GL_DEPTH_COMPONENT, so make sure we only allocate 1 bit per pixel.
	if(channels > 4)
		channelsTrue = 1;

	// Allocate and init memory for the image array and point to it from pTexture
	pTexture = new unsigned int [sizeX * sizeY * channelsTrue];
	memset(pTexture, 0, sizeX * sizeY * channelsTrue * sizeof(unsigned int));	

	// Register the texture with OpenGL and bind it to the texture ID
	glGenTextures(1, &shadowMapTexture);								
	glBindTexture(GL_TEXTURE_2D, shadowMapTexture);					

	// Create the texture and store it on the video card
	glTexImage2D(GL_TEXTURE_2D, 0, channels, sizeX, sizeY, 0, type, GL_UNSIGNED_INT, pTexture);						

	// Set the texture quality
	glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	// What do these do?
	//glTexParameteri(GL_TEXTURE_2D,GL_DEPTH_TEXTURE_MODE,GL_INTENSITY);
	//glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_COMPARE_MODE,GL_NONE);

	// Since we stored the texture space with OpenGL, we can delete the image data
	delete [] pTexture;																					
}

void ShadowManager::beginFirstPass()
{
	UpdateShadowLightMatrices();

	if( slowTextureCopyMode )
	{

	}else{
		frameBufferObject.begin();
	}

	// Clear the screen and depth buffer so we can render from the light's view
	glClear(GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	// Set the current viewport to our texture size

	glViewport(0, 0, (int)shadowWid, (int)screenHei);

	// Now we just need to set the matrices for the light before we render
	glMatrixMode(GL_PROJECTION);

	// Push on a matrix to make sure we can restore to the old matrix easily
	glPushMatrix();

	// Set the current projection matrix to our light's projection matrix
	glLoadMatrixf(g_mProjection);

	// Load modelview mode to set our light's modelview matrix
	glMatrixMode(GL_MODELVIEW);

	// Load the light's modelview matrix before we render to a texture
	glLoadMatrixf(g_mModelView);

	// Since we don't care about color when rendering the depth values to
	// the shadow-map texture, we disable color writing to increase speed.
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 

	// Eliminate artifacts caused by shadow mapping
	glPolygonOffset(offsetFactor, offsetUnits);
	glEnable(GL_POLYGON_OFFSET_FILL);
}

void ShadowManager::endFirstPass()
{
	glDisable(GL_POLYGON_OFFSET_FILL);

	// Now that the world is rendered, save the depth values to a texture
	if( slowTextureCopyMode )
	{
		glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
		glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, (int)shadowWid, (int)shadowHei);
	}else
	{
		frameBufferObject.end();
		//glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, frameBufferObject.depth_rb );
	}


	// We can turn color writing back on
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 

	// Restore our normal viewport size to our screen width and height
	glViewport(0, 0, (int)screenWid, (int)screenHei);

	// Go back to the projection mode and restore the original matrix
	glMatrixMode(GL_PROJECTION);

	// Restore the original projection matrix
	glPopMatrix();

	// Go back to modelview model to start drawing like normal
	glMatrixMode(GL_MODELVIEW);
}

// Now we read from depth texture/buffer
void ShadowManager::beginLastPass(float inverseCameraMat[16])
{
	memcpy( g_mCameraInverse, inverseCameraMat, sizeof(float) * 16);
	g_Shader.begin();

	// Turn on our texture unit for shadow mapping and bind our depth texture

	if(slowTextureCopyMode)
	{
		glActiveTexture(GL_TEXTURE0);
		glEnable(GL_TEXTURE_2D); 
		glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
	}else{
		//glDrawBuffer(GL_COLOR_ATTACHMENT0); // todo. this has something to do with renderspeed...
		//glReadBuffer(GL_COLOR_ATTACHMENT0);
		glBindTexture(GL_TEXTURE_2D, frameBufferObject.depth_rb); // slow?
		frameBufferObject.end();
	}

	// give shader access, since we use GL_TEXTURE0_ARB we pass in 0.
	GLuint uniform = glGetUniformLocation(g_Shader.getProgram(), "shadowMap"); 
	glUniform1i(uniform, 0);

	// Here is where we set the mode and function for shadow mapping with shadow2DProj().
	// shadow2DProj() requires ARB modes for texture paramaters so it knows what to do to the shadowmap.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

	// Create our bias matrix to have a 0 to 1 ratio after clip space
	const float mBias[] = {0.5, 0.0, 0.0, 0.0,  
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
		0.5, 0.5, 0.5, 1.0};

	glMatrixMode(GL_TEXTURE);
	// Remember this:
	// mProjectedTexture = mLightProjection * mLightModelview * mCameraInverse;
	float result[16];
	glusMultMatrixf( result, mBias, g_mProjection);
	glusMultMatrixf( result, result, g_mModelView);
	glusMultMatrixf( result, result, g_mCameraInverse);
	glLoadMatrixf( result ); // kunne sende til shader med uniform

	glMatrixMode(GL_MODELVIEW);			// Switch back to normal modelview mode
}

void ShadowManager::endLastPass()
{
	if( slowTextureCopyMode )
	{

	}else{
		frameBufferObject.end();
	}

	// Now that the world is shadowed and we are done with the texture generation,
	// let's set everything back to normal by resetting the texture matrix.
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);

	// Turn the first multi-texture pass off
	glActiveTexture(GL_TEXTURE0);		
	glDisable(GL_TEXTURE_2D);

	g_Shader.end();
}

void ShadowManager::debugRender()
{
// DEBUG only. this piece of code draw the depth buffer onscreen
	glUseProgram(0);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	{
		glLoadIdentity();
		glOrtho(-screenWid/2,screenWid/2,-screenHei/2,screenHei/2,1,20);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		{
			glLoadIdentity();
			glColor4f(1,1,1,1);
			glActiveTexture(GL_TEXTURE0);
			if( slowTextureCopyMode )
			{
				glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
			}else
			{
				glBindTexture(GL_TEXTURE_2D, frameBufferObject.depth_rb);
			}
			glEnable(GL_TEXTURE_2D);
			glTranslated(0,0,-1);
			
			glBegin(GL_QUADS);
			{
				glTexCoord2d(0,0);glVertex3f(0.0f,0.0f,0.0f);
				glTexCoord2d(1,0);glVertex3f(screenWid/2.0f,0.0f,0.0f);
				glTexCoord2d(1,1);glVertex3f(screenWid/2.0f,screenHei/2.0f,0.0f);
				glTexCoord2d(0,1);glVertex3f(0.0f,screenHei/2.0f,0.0f);
			}glEnd();

			glDisable(GL_TEXTURE_2D);
		}glPopMatrix(); 
		glMatrixMode(GL_PROJECTION);
	}glPopMatrix();
	glMatrixMode(GL_MODELVIEW);


	///////////////////////

//	glUseProgram(0);
//	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//
//	glDisable(GL_LIGHTING);
//
//	//glViewport(0, 0, screenWid, screenHei);
//
//	glMatrixMode(GL_TEXTURE);
//	glLoadIdentity();
//
//	glEnable(GL_TEXTURE_2D);
//	if( slowTextureCopyMode )
//	{
//		glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
//	}else
//	{
//		glBindTexture(GL_TEXTURE_2D, frameBufferObject.shadowMap);
//	}
//	
//	//insert texture attributes here or during texture construction, whichever you prefer:
//	glTexParameteri(GL_TEXTURE_2D,GL_DEPTH_TEXTURE_MODE,GL_INTENSITY);
//	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_COMPARE_MODE,GL_NONE);
//
//	glMatrixMode(GL_MODELVIEW); 
//	glPushMatrix();
//	{
//		glLoadIdentity(); 
//		glMatrixMode(GL_PROJECTION); 
//		glPushMatrix();
//		{
//			glLoadIdentity ();
//			glBegin(GL_QUADS);
//			{	
//				glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);  
//				glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);   
//				glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 0.0f);    
//				glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f);   
//			}
//			glEnd();
//		}
//		glPopMatrix(); 
//		glMatrixMode(GL_MODELVIEW);
//	}
//	glPopMatrix();
//
//	glEnable(GL_LIGHTING);
//	glDisable(GL_TEXTURE_2D);
//}


}